//
//  STJailbreakTools.swift
//  Source_iOS
//
//  Created by Toj on 5/12/22.
//

import UIKit

open class STJailbreakTools: NSObject {
    
    public static let shared = STJailbreakTools()
    
    // 是否检索过, 下次直接从缓存读取
    private var isDetected = false
    // 缓存的值, 下次直接从缓存读取
    private var _isJailbroken = false
    public var isJailbroken: Bool {
        if !isDetected {
            isDetected = true
            _isJailbroken = detectIsJailbroken()
        }
        
        return _isJailbroken
    }
    
    // 6种检查是否越狱的方法, 只要命中一个, 就说明已经越狱.
    private func detectIsJailbroken() -> Bool {
        // 1判定: 常见的越狱文件
        var result = jailbreakFileExisted
        if result {
            return result
        }
        
        // 2判定: 能否读取文件内容, 文件受保护
        result = evidenceOfSymbolLinking
        if result {
            return result
        }
        
        // 3判定: 私有文件夹内能否写文件
        result = sandboxBreached
        if result {
            return result
        }
        
        // 4判定: cydia的URL scheme.
        result = cydiaPathExisted
        if result {
            return result
        }
        
        // 5判定: 读取系统所有应用的名称
        result = appPathExisted
        if result {
            return result
        }
        
        // 6判定: 环境变量DYLD_INSERT_LIBRARIES 值
        result = environmentExisted
        return result
    }
}

public extension STJailbreakTools {
    /**
     * 1. 判定常见的越狱文件
     * /Applications/Cydia.app
     * /Library/MobileSubstrate/MobileSubstrate.dylib
     * /bin/bash
     * /usr/sbin/sshd
     * /etc/apt
     * /private/var/lib/apt/
     * 这个表可以尽可能的列出来，然后判定是否存在，只要有存在的就可以认为机器是越狱了。
     */
    private var jailbreakFileExisted: Bool {
        let jailbreakFilePaths = [
            "/etc/apt",
            "/bin/bash",
            "/usr/sbin/sshd",
            "/private/var/lib/apt/",
            "/Applications/Cydia.app",
            "/Library/MobileSubstrate/MobileSubstrate.dylib"
        ]
        
        let fileManager = FileManager.default
        let result = jailbreakFilePaths.contains { path in
            if fileManager.fileExists(atPath: path) {
                return true
            }
            if let file = fopen(path, "r") {
                fclose(file)
                return true
            }
            return false
        }
        
        return result
    }
    
    /**
     * 2. 能否读取文件内容, 文件受保护
     */
    private var evidenceOfSymbolLinking: Bool {
        var s = stat()
        guard lstat("/Applications", &s) == 0 else { return false }
        /* st_mode: 文件保护模式 */
        return (s.st_mode & S_IFLNK == S_IFLNK)
    }

    /**
     * 3. 私有文件夹内能否写文件
     */
    private var sandboxBreached: Bool {
        guard (try? " ".write(
            toFile: "/private/jailbreak.txt",
            atomically: true, encoding: .utf8)) == nil else {
                return true
        }
        return false
    }
    
    /**
     * 4. 判断cydia的URL scheme.
     */
    var cydiaPathExisted: Bool {
        guard let cydiaURL = URL(string: "cydia://") else { return false }
        if UIApplication.shared.canOpenURL(cydiaURL) {
            return true
        }
        
        return false
    }
    
    /**
     * 5. 读取系统所有应用的名称.
     * 这个是利用不越狱的机器没有这个权限来判定的。
     */
    var appPathExisted: Bool {
        let path = "/User/Applications/"
        if FileManager.default.fileExists(atPath: path) {
            return true
        }
        return false
    }
    
    /**
     * 6. 根据环境变量DYLD_INSERT_LIBRARIES 值, 判断是否越狱
     * 非越狱的机器上应该是空,
     * 越狱的机器上基本都会有Library/MobileSubstrate/MobileSubstrate.dylib.
     */
    var environmentExisted: Bool {
       return SENVIRNames.libraries.getenv.count > 0
    }
}
